/*
* This file is part of QSerialDevice, an open-source cross-platform library
* Copyright (C) 2009  Denis Shienkov
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
* Contact Denis Shienkov:
*          e-mail: <scapig2@yandex.ru>
*             ICQ: 321789831
*/


#include <QtCore/QStringList>
#include <QtCore/QSocketNotifier>

#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>

extern "C"
{
    #include <libudev.h>
}

#include "serialdeviceenumerator.h"
#include "serialdeviceenumerator_p.h"
#include "../qserialdevice/ttylocker.h"

//#define SERIALDEVICEENUMERATOR_UNIX_DEBUG

#ifdef SERIALDEVICEENUMERATOR_UNIX_DEBUG
#include <QtCore/QDebug>
#endif


SerialDeviceEnumeratorPrivate::SerialDeviceEnumeratorPrivate()
    : m_eventNotifier(0)
{
    this->udev = ::udev_new();
    if (!this->udev) {
#if defined (SERIALDEVICEENUMERATOR_UNIX_DEBUG)
        qDebug() << "Unix: SerialDeviceEnumeratorPrivate() \n"
                " -> function: ::udev_new() returned: 0 \n";
#endif
        return;
    }

    this->udev_monitor = ::udev_monitor_new_from_netlink(this->udev, "udev");
    if (!this->udev_monitor) {
#if defined (SERIALDEVICEENUMERATOR_UNIX_DEBUG)
        qDebug() << "Unix: SerialDeviceEnumeratorPrivate() \n"
                " -> function: ::udev_monitor_new_from_netlink() returned: 0 \n";
#endif
        return;
    }

    ::udev_monitor_filter_add_match_subsystem_devtype(this->udev_monitor, "tty", 0);
    ::udev_monitor_enable_receiving(this->udev_monitor);
    this->udev_socket = ::udev_monitor_get_fd(this->udev_monitor);
}

SerialDeviceEnumeratorPrivate::~SerialDeviceEnumeratorPrivate()
{
    if (this->m_eventNotifier)
        this->m_eventNotifier->setEnabled(false);

    if (-1 != this->udev_socket) {
        //qt_safe_close(this->udev_socket);
        ::close(this->udev_socket);
        this->udev_socket = -1;
    }

    if (this->udev_monitor)
        ::udev_monitor_unref(this->udev_monitor);

    if (this->udev)
        ::udev_unref(this->udev);
}

void SerialDeviceEnumeratorPrivate::setEnabled(bool enable)
{
    Q_Q(SerialDeviceEnumerator);

    if (!this->m_eventNotifier) {
        if (-1 == this->udev_socket)
            return;
        this->m_eventNotifier = new QSocketNotifier(this->udev_socket, QSocketNotifier::Read, q);
        q->connect(this->m_eventNotifier, SIGNAL(activated(int)), q, SLOT(_q_processWatcher()));
    }

    if (!this->isValid())
        return;

    this->m_eventNotifier->setEnabled(enable);

    if (enable)
        this->_q_processWatcher();
}

bool SerialDeviceEnumeratorPrivate::isEnabled() const
{
    return (this->isValid() && this->m_eventNotifier->isEnabled());
}

bool SerialDeviceEnumeratorPrivate::isBusy() const
{
    bool ret = false;
    QString path = this->name();
    if (path.isEmpty())
        return ret;

    TTYLocker locker;
    locker.setDeviceName(path);

    bool byCurrPid = false;
    ret = locker.locked(&byCurrPid);

    return ret;
}

QMap<QString, SerialInfo> SerialDeviceEnumeratorPrivate::updateInfo() const
{
    QMap<QString, SerialInfo> info;
    struct udev_enumerate *enumerate = ::udev_enumerate_new(this->udev);
    if (!enumerate) {
#if defined (SERIALDEVICEENUMERATOR_UNIX_DEBUG)
        qDebug() << "Unix: SerialDeviceEnumeratorPrivate::updateInfo() \n"
                " -> function: ::udev_enumerate_new() returned: 0 \n";
#endif
        return info;
    }

    struct udev_list_entry *devices, *dev_list_entry;

    ::udev_enumerate_add_match_subsystem(enumerate, "tty");
    ::udev_enumerate_scan_devices(enumerate);

    devices = ::udev_enumerate_get_list_entry(enumerate);

    udev_list_entry_foreach(dev_list_entry, devices) {
        const char *syspath = ::udev_list_entry_get_name(dev_list_entry);

        struct udev_device *udev_device = ::udev_device_new_from_syspath(this->udev, syspath);

        if (udev_device) {

            SerialInfo si;

            //get device name
            QString s(::udev_device_get_devnode(udev_device));

            if (s.contains("ttyS")
                || s.contains("ttyUSB")
                || s.contains("ttyACM") ) {

                //description
                si.m_description = QString(::udev_device_get_property_value(udev_device, "ID_MODEL_FROM_DATABASE"));
                //driver
                si.m_driverName = QString(::udev_device_get_driver(udev_device));
                //friendly name
                si.m_friendlyName = si.m_description + " (" + si.m_shortName +")";
                //hardware ID
                si.m_hardwareID = QStringList();
                //location info
                si.m_locationInfo = QString(::udev_device_get_property_value(udev_device, "ID_MODEL_ENC"))
                                    .replace("\\x20", QString(" "));
                //manufacturer
                si.m_manufacturer = QString(::udev_device_get_property_value(udev_device, "ID_VENDOR_FROM_DATABASE"));
                //sub system
                si.m_subSystem = QString(::udev_device_get_property_value(udev_device, "SUBSYSTEM"));
                //service
                si.m_service = QString();
                //system path
                si.m_systemPath = QString(::udev_device_get_syspath(udev_device));
                //product ID
                si.m_productID = QString(::udev_device_get_property_value(udev_device, "ID_MODEL_ID"));
                //vendor ID
                si.m_vendorID = QString(::udev_device_get_property_value(udev_device, "ID_VENDOR_ID"));
                //short name
                si.m_shortName = QString(::udev_device_get_property_value(udev_device, "DEVNAME"));

                //add to map
                info[s] = si;
            }

            ::udev_device_unref(udev_device);
        }
    }

    ::udev_enumerate_unref(enumerate);

    return info;
}

//compare two maps.
//if maps is not equal then function return true
static bool compareNotEqSerialInfoMaps(const QMap<QString, SerialInfo> &map1, const QMap<QString, SerialInfo> &map2)
{
    if (map1.size() != map2.size())
        return true;
    if (map1.keys() != map2.keys())
        return true;

    QList<SerialInfo> l1 = map1.values();
    QList<SerialInfo> l2 = map2.values();

    int size = l1.size();//here l1.size() == l2.size()!!!
    while (size--) {

        SerialInfo si1 = l1.at(size);
        SerialInfo si2 = l2.at(size);

        if ((si1.m_description != si2.m_description)
            || (si1.m_driverName != si2.m_driverName)
            || (si1.m_friendlyName != si2.m_friendlyName)
            || (si1.m_hardwareID != si2.m_hardwareID)
            || (si1.m_locationInfo != si2.m_locationInfo)
            || (si1.m_manufacturer != si2.m_manufacturer)
            || (si1.m_productID != si2.m_productID)
            || (si1.m_service != si2.m_service)
            || (si1.m_shortName != si2.m_shortName)
            || (si1.m_subSystem != si2.m_subSystem)
            || (si1.m_systemPath != si2.m_systemPath)
            || (si1.m_vendorID != si2.m_vendorID)) {

            return true;
        }

    }
    return false;
}

void SerialDeviceEnumeratorPrivate::_q_processWatcher()
{
    Q_Q(SerialDeviceEnumerator);

    if (!this->isValid())
        return;

    QMap<QString, SerialInfo> info = this->updateInfo();

    if (compareNotEqSerialInfoMaps(info, this->m_map)) {
        this->m_map = info;
        emit q->hasChanged(info.keys());
    }
}

bool SerialDeviceEnumeratorPrivate::isValid() const
{
    return ( this->udev && this->udev_monitor && (-1 != this->udev_socket) );
}


#include "moc_serialdeviceenumerator.cpp"

